/* 
 * File:   DielectricShader.cpp
 * Author: brady
 * 
 * Created on August 10, 2011, 7:11 PM
 */
#include <shaders/DielectricShader.h>

#ifndef REFRACT
#define REFRACT 1
#endif

#ifndef REFLECT
#define REFLECT 0
#endif

DielectricShader::DielectricShader() {
}

DielectricShader::DielectricShader(const DielectricShader& orig) {
}

DielectricShader::~DielectricShader() {
}

Vector3D DielectricShader::shade(IntersectionResult& result) const {
    //the sum of the light hitting the object
    //Vector3D totalLight = computeLighting(result);
    
    Vector3D totalLight(0,0,0);
    Vector3D highlights(0,0,0);
    Vector3D viewDir = result.ray.getDirection() * -1;
    const Vector3D& point(result.hitPoint);
    const vector<Light*>& lights = gParams.scene->lights;

    int lightCount = lights.size();
    for(int i = 0; i < lightCount; i++){
        Light* curLight = lights[i];
        Vector3D samplePoint = curLight->getSamplePoint(result);
        double shadowCoeff = 0;
            Vector3D toLight = samplePoint - point;
            double lightDistance = toLight.magnitude();
            toLight.normalize();

            Ray lightSampleRay = Ray(result.hitPoint, toLight);

            //Test for shadows
            //if lightray hits an object, shadow coeff = 0.0
            //otherwise compute the shadow coefficient to unobstructed light
            IntersectionParams shadowParams(lightSampleRay, FLT_EPSILON, lightDistance);
            IntersectionResult occluder = gParams.scene->intersectionStructure->getAnyIntersection(shadowParams);
            if (occluder.shape != NULL) {
                shadowCoeff = 0.0;
            }
            else {
                    shadowCoeff = getLambertianCoeffecient(result.normal, toLight);
                    //phong component
                    Vector3D h = viewDir.bisect(toLight);
                    double c = h.dot(result.normal);
                    double highlightCoeff = pow(c, phongExponent);
                    highlights += specularColor.mix(curLight->getColor() * highlightCoeff);
            }
        totalLight += (curLight->getColor() * shadowCoeff);
    }
    
    
    //pull ray refraction out of computeRefractions.
    //then we can compute schlicks out here and blend accordingly
    Ray refractedRay = result.ray.refract(result.normal, result.hitPoint, refractiveIndex);
    //the color of the object at the point of intersection
    Vector3D pointColor = result.shape->texture->getColor(result);
    Vector3D finalColor = pointColor.mix(totalLight);
    
    finalColor = finalColor.weightedAverage(
            //importance blends reflections and refractions
            importance(result.ray, refractedRay, result), transparency)
            + highlights;
    return finalColor;

}

Vector3D DielectricShader::computeRefractions(IntersectionResult& result, const Ray& refractedRay) const{
    Vector3D refraction = Vector3D(0.0, 0.0, 0.0);
    if (transparency > 0.0 && result.ray.recursionLimit > 0) {
        //determine side of shape
        IntersectionParams ip(refractedRay, FLT_EPSILON, 100 ); //TODO make tmax a parameter
        IntersectionResult refractionResult = gParams.scene->intersectionStructure->getFirstIntersection(ip);
        if(refractionResult.shape != NULL){
            refraction = refractionResult.shape->getShader()->shade(refractionResult);
        }
        else{
            refraction = gParams.scene->backgroundColor;
        }
    }
    return refraction;
}

double DielectricShader::schlickReflectance(const Ray& ray_i, const Ray& ray_t, const Vector3D& normal) const{
    Vector3D adjustedNormal(normal);
    double n1 = 1.0;
    double n2 = refractiveIndex;
    if(ray_i.getDirection().dot(normal) > 0){
        adjustedNormal *= -1;
        double temp = n1;
        n1 = n2;
        n2 = temp;
    }
    double schlickReflectance = 1;
    double r0 = (n1 - n2)/(n1 + n2);
    r0 = r0*r0;
    double cosI = -ray_i.getDirection().dot(adjustedNormal);
    //if cosT is < 0, it was reflected
    double cosT = -ray_t.getDirection().dot(adjustedNormal);

    if(n1 <= n2){
        double p = (1-cosI);
        schlickReflectance = r0 + (1-r0)*(p*p*p*p*p);
    }
    else if (cosT > 0){
        double p = (1-cosT);
        schlickReflectance = r0 + (1-r0)*(p*p*p*p*p);
    }
    else{ //TIR, we reflected the ray already
        schlickReflectance = 1;
    }
    return schlickReflectance;
}

Ray DielectricShader::roulette(const Ray& ray_i, IntersectionResult& result) const {
    Ray ray_t = ray_i.refract(result.normal, result.hitPoint, refractiveIndex);
    double schlick_r = schlickReflectance(ray_i, ray_t, result.normal);

    //sampleValue is sampleNumber + [0,1]/initialSamples
    //this garuntees us reflectance accuracy
    //within 1/n of true reflectance proportion where n is number of samples

    double randShift = (double)random()/(double)RAND_MAX;
    randShift *= (1.0/(double)gParams.initialSamples);

    double sampleValue = ((double)ray_i.sampleNumber/(double)gParams.initialSamples) + randShift;

    if(sampleValue > schlick_r){
        return ray_t;
    }
    else{
        return ray_i.reflect(result.normal, result.hitPoint);
    }
}

Vector3D DielectricShader::importance(const Ray& ray_i, const Ray& ray_t, IntersectionResult& result) const{
    double schlick_r = schlickReflectance(ray_i, ray_t, result.normal);

    //sampleValue is sampleNumber + [0,1]/initialSamples
    //this garuntees us reflectance accuracy
    //within 1/n of true reflectance proportion where n is number of samples

    double randShift = (double)random()/(double)RAND_MAX;
    randShift *= (1.0/(double)gParams.initialSamples);

    double sampleValue = ((double)ray_i.sampleNumber/(double)gParams.initialSamples) + randShift;

    if(sampleValue > schlick_r){
        return computeRefractions(result,ray_t);
    }
    else{
        return computeReflections(result);
    }
}
